home *** CD-ROM | disk | FTP | other *** search
- Date: 26 February 1988
- From: Adrian Godwin, 78 Putnoe Street, Bedford, England.
- Subject: Additional C-Kermit Implementation Notes for Minix
-
- Modifying C-Kermit 4D-061 for use under Andrew Tanenbaum's 'MINIX' has
- required rather more changes to Minix than to Kermit. The C source files are
- included; they all began as the CK---.--- files for the 4D(061) distribution
- set. Here the names have been changed to MX---.--- . Hints, fixes and library
- changes are also attached - most of these are applicable for anyone
- implementing a serial i/o driver for Minix, and many library fixes are useful
- for porting other utilities.
-
- Kermit cannot be built under version 1.1 Minix, as it compiles to
- about 85K and the initial Minix assembler cannot separate I&D model output.
- The executable file was therefore built under MS-DOS using the Lattice 3.10 C
- compiler. Some care is needed in cross-compiling : see the notes in Tanenbaum's
- book about libraries, and read the enclosed Lattice makefile, cktker.mak.
-
- A port of this version to the latest C-Kermit version 4E(070) is now underway
- and will be released at some future time.
-
- Associated Files:
- =================
-
- I have included with this help file (cktker.hlp) the following :
-
- mxcmai.c - Modified Kermit source files
- mxutio.c
- mxufio.c
- mxuusr.c
- mxuus3.c
- mxcfns.c
-
- mxtker.mak - Makefile for Lattice 'lmk' and 3.10 compiler.
-
- mxtker.inp - linker command file (included in mxtker.mak)
-
- mxtker.boo - boo encoding of cktker.out
-
-
- Changes to Kermit:
- ==================
-
- ckutio.c As usual, most of the changes are here. Fixes are largely
- a matter of picking a suitable option from the variety of
- code that exists. Use V7, but enable use of MYREAD and DON'T
- use the kmem (initrawq) functions. The uucp locks directory
- doesn't exist, but since it's a PC and you'll know if you're
- using the serial port, remove locking code altogether.
- For some reason, Lattice returns the unsigned char result
- of myread() expanded to a signed int. A badly read character
- can thus appear to other functions as an error code. Make
- myread return ((int)ch) & 0377.
- Ttychk and conchk need fixing to stop the accesses to kmem.
- Reorder ttychk so that if myread() finds nothing in it's
- buffer, it will execute other (system dependent) code,
- like FIONREAD, to determine if typahead exists in the system
- buffers. Then implement a FIONREAD ioctl call if you want.
- It makes a slight improvement to 'connect' performance, but
- is more worthwhile for conchk(), where nonblocking read is
- no use. The transfer escape characters can then be used.
-
- ckcmai.c Shorten the help message provided for remote help -
- Lattice can only compile strings less than 256 bytes long.
- Why isn't this list compiled from the command tables, like
- the command line interpreter's command completion?
-
- ckuusr.c Minix has a curious interpretation of printf, requiring
- ckuus3.c "%D" to indicate a long integer. Change all the "%ld" formats
- in these two files, or better still fix the do_printf() in the
- Minix library to accept K&R formats.
- Also calculate efficiency, etc. for statistics using long
- constants - Lattice makes a hash of it otherwise.
-
- ckcfns.c Calculate effective speed & efficiency for tlog using
- specific long arithmetic. Use a long for x, the argument
- for F101 in TLOG.
-
- ckufio.c No include file <sys/files.h>. Lattice would prefer the
- functions FILE *fopen(), *fdopen() to be declared as such.
- The function zclosif() attempts to kill a completed child
- process. If it has already completed, this fails, but MINIX
- still wants a wait() call to clean up the memory allocation,
- otherwise a fwe invocations of 'remote directory' will use
- up any remaining memory. This problem will also occur in the
- minix shell if you invoke all processes in the background.
- Use the shell command 'wait' to clean up completed processes
- (zombies).
-
-
- Changes to Minix library routines.
- ==================================
-
- I started developing this set of library fixes when building Micro-
- Emacs. A few of them may be unnecessary, but I recommend you fix them anyway.
- Many routines are here because they aren't in the standard library.
- The lattice makefile includes all these routines as an object called 'emlib' -
- this is for debugging purposes only (they should be built into the library).
- While not strictly a library change, fix dos2out before attempting to
- convert this large executable : the calculation of load_size will be wrong
- unless all the operands are forced to type long. The calculation of the
- total memory allocation, a_totb is also wrong, since it includes (for the
- separate I & D model) the text size a_text in the sum. This results in a
- 154K memory allocation, which is not permitted. chmem =20000 will fix it.
-
- Some include file changes. Stdio.h defines a macro 'puts()' as an
- fputs() to stdout. This is wrong, as puts() should append a newline while
- fputs() doesn't. Remove the macro and create a library function.
-
- There is no <sys/dir.h>. It should contain:
-
- /* dir.h
- *
- * The structure of a directory entry.
- */
-
- #define NAME_SIZE 14 /* defined in fs/const.h */
-
- struct direct {
- inode_nr d_ino; /* inode number */
- char d_name[NAME_SIZE]; /* name fills it out to 16 bytes */
- } ;
-
- The file h/type.h is needed by kermit, but included as <sys/types.h>.
- Create a <sys/types.h> that includes h/type.h.
-
- Sgtty.h also needs some changes to define tty functions that don't
- exist in standard Minix. The kermit I have built used this version of sgtty.h,
- so if you want to use that executable, your tty driver must correspond. The
- i/o functions are an upwards compatible superset of the Minix sgtty functions,
- and are inspired by SCO Xenix. Note particularly the implementation of
- stty() and gtty() as macros.
-
- /* sgtty.h
- *
- * Data structures for IOCTL.
- */
-
- struct sgttyb {
- char sg_ispeed; /* input speed (has precedence) */
- char sg_ospeed; /* output speed (may be ignored) */
- char sg_erase; /* erase character */
- char sg_kill; /* kill character */
- int sg_flags; /* mode flags */
- };
-
- /* simulate stty and gtty with ioctl */
-
- #define stty(fd, arg) ioctl(fd, TIOCSETP, arg)
- #define gtty(fd, arg) ioctl(fd, TIOCGETP, arg)
-
-
- struct tchars {
- char t_intrc; /* SIGINT char */
- char t_quitc; /* SIGQUIT char */
- char t_startc; /* start output (initially CTRL-Q) */
- char t_stopc; /* stop output (initially CTRL-S) */
- char t_eofc; /* EOF (initially CTRL-D) */
- char t_brkc; /* input delimiter (like nl) */
- };
-
- /* Fields in sg_flags. */
-
- #define COOKED 0000000 /* neither CBREAK nor RAW */
- #define TANDEM 0000001 /* XON XOFF flowcontrol on input */
- #define CBREAK 0000002 /* enable cbreak mode */
- #define LCASE 0000004 /* support lower case */
- #define ECHO 0000010 /* echo input */
- #define CRMOD 0000020 /* map lf to cr + lf */
- #define RAW 0000040 /* enable raw mode */
- #define ODDP 0000100 /* odd parity */
- #define EVENP 0000200 /* even parity */
- #define ANYP 0000300 /* parity not set / ignored */
- #define XTABS 0006000 /* do tab expansion */
-
-
- #define TIOCGETP (('t'<<8) | 8)
- #define TIOCSETP (('t'<<8) | 9)
- #define TIOCGETC (('t'<<8) | 18)
- #define TIOCSETC (('t'<<8) | 17)
-
- /*
- * I can't find a 'standard' definition for this one, so I've assigned
- * a '1' arbitrarily.
- */
-
- #define FIONREAD (('f'<<8) | 1)
-
- /* Baud rate settings for async ttys. */
-
- #define B0 0 /* hangup line (drop DTR) */
- #define B50 1
- #define B75 2
- #define B110 3
- #define B134 4
- #define B150 5
- #define B200 6
- #define B300 7
- #define B600 8
- #define B1200 9
- #define B1800 10
- #define B2400 11
- #define B4800 12
- #define B9600 13
-
- #define EXTA 14
- #define B19200 EXTA
-
- #define EXTB 15
- #define B38400 EXTB
-
-
- There are no time management calls in MINIX other than time, times()
- etc. I have defined asctime() and gmtime() functions based on the conversions
- done in date.c, but avoided support for timezones and daylight saving.
-
-
- /* time.h : structure and function definitions for time library calls. */
-
- struct tm {
- int tm_sec;
- int tm_min;
- int tm_hour;
- int tm_mday;
- int tm_mon;
- int tm_year;
- int tm_wday;
- int tm_yday;
- int tm_isdst; /* not currently supported */
- };
-
- extern struct tm *localtime();
- extern struct tm *gmtime();
- extern char *asctime();
-
-
- /* system(command)
- *
- * Code copied from the MINIX make utility 'mysystem' function, with
- * minor modifications.
- */
-
- char *_defpath = "/bin/sh";
-
- system(cmd)
- char *cmd;
- {
- int ccode,pid,status;
- char *shell, *getenv();
-
- if ( (shell = getenv("SHELL")) == NULL)
- shell = _defpath;
-
- if ( (pid = fork()) == 0 ) { /* child execs a shell */
- execl(shell,shell,(*cmd ? "-c" : "-i"),cmd,0);
- }
-
- if ( pid < 0 ) { /* parent waits for child */
- return(pid);
- }
- else {
- while ( ((ccode = wait(&status)) != pid) && (ccode != -1))
- ;
- return(status);
- }
- }
-
-
-
- /* This function copied from the MINIX library, but with some bugs fixed. */
-
- char *getenv(name)
- register char *name;
- {
- extern char **environ;
- register char **v = environ, *p, *q;
-
- while ((p = *v++) != NULL) { /* fix : increment v */
- q = name;
- while (*p++ == *q)
- if (*q++ == 0)
- break; /* fix : break rather than continue */
- if (*(p - 1) != '=')
- continue;
- return(p);
- }
- return(0);
- }
-
-
-
- /* getc
- *
- * fixed to ensure _count always indicates number of chars in the buffer.
- * - used to indicate correctly only when zero. Decrement _count after
- * taking char from recently filled buffer. Note : fseek() had a bodge
- * which allowed it to work with this error. This also requires a fix.
- *
- * note when rebuilding library : this module should appear AFTER scanf,
- * which calls it, to ensure it can be found.
- */
-
- #include "stdio.h"
-
- getc(iop)
- FILE *iop;
- {
- int ch;
-
- if ( testflag(iop, (_EOF | _ERR )))
- return (EOF);
-
- if ( !testflag(iop, READMODE) )
- return (EOF);
-
- if (--iop->_count < 0){ /* changed from <= 0 */
-
- if ( testflag(iop, UNBUFF) )
- iop->_count = read(iop->_fd,&ch,1);
- else
- iop->_count = read(iop->_fd,iop->_buf,BUFSIZ);
-
- if (iop->_count <= 0){
- if (iop->_count == 0)
- iop->_flags |= _EOF;
- else
- iop->_flags |= _ERR;
-
- return (EOF);
- }
- else {
- iop->_ptr = iop->_buf;
- iop->_count--; /* inserted */
- }
- }
-
- if (testflag(iop,UNBUFF))
- return (ch & CMASK);
- else
- return (*iop->_ptr++ & CMASK);
- }
-
- /* ttyname() - missing library function. Gets the inode number of the
- * passed filedescriptor, then searches the /dev directory for a match.
- * Returns a pointer to the name (static data) or NULL.
- */
-
- #include "stdio.h"
- #include "stat.h"
- #include "dir.h"
-
- char *ttyname(fd)
- int fd;
- {
- struct stat s;
- int n, dd;
- char *p = NULL;
- static struct direct d[2]; /* big enough to nul-terminate name */
-
- if (fstat(fd, &s) != 0 || (s.st_mode&S_IFMT) != S_IFCHR)
- return(NULL);
-
- /* isatty. now search /dev for the inode. */
-
- n = s.st_ino;
- if ((dd = open("/dev", 0)) < 0)
- return NULL; /* cannot read directory */
-
- while (read( dd, d, sizeof(*d)) == sizeof(*d))
-
- if (d[0].d_ino == n) {
- p = d[0].d_name;
- p[sizeof(*d)] = '\0';
- break;
- }
- close(dd);
- return(p);
- }
-
- /* fdopen() - missing library function. Returns a file pointer set up from
- * the passed file descriptor, or NULL if it can't.
- */
-
- #include "stdio.h"
-
- FILE *fdopen(fd,mode)
- int fd;
- char *mode;
- {
- register int i;
- FILE *fp;
- char *malloc();
- int flags = 0;
-
- for (i = 0; _io_table[i] != 0 ; i++)
- if ( i >= NFILES )
- return(NULL);
-
- switch(*mode){
-
- case 'w':
- case 'a':
- flags |= WRITEMODE;
- break;
-
- case 'r':
- flags |= READMODE;
- break;
-
- default:
- return(NULL);
- }
-
-
- if (( fp = (FILE *) malloc (sizeof( FILE))) == NULL )
- return(NULL);
-
- fp->_count = 0;
- fp->_fd = fd;
- fp->_flags = flags;
- fp->_buf = malloc( BUFSIZ );
- if ( fp->_buf == NULL )
- fp->_flags |= UNBUFF;
- else
- fp->_flags |= IOMYBUF;
-
- fp->_ptr = fp->_buf;
- _io_table[i] = fp;
- return(fp);
- }
-
- /* modified exit() which flushes stdio buffers before dying. This means
- * standard io library is included whether you wanted it or not - add a
- * dummy "_cleanup() {}" declaration to avoid it if you're writing very
- * small programs like the Minix utilities. This is more likely to
- * be compatible with other UNIX programs than the exit() in the standard
- * release.
- */
-
- #include "../include/lib.h"
-
- int exit(status)
- int status;
- {
- _cleanup();
- return callm1(MM, EXIT, status, 0, 0, NIL_PTR, NIL_PTR, NIL_PTR);
- }
-
-
- /* strncmp() - library module, changed to operate correctly when
- * n is zero. (causes problem in kermit interactive command processor)
- */
-
- int strncmp(s1, s2, n)
- register char *s1, *s2;
- int n;
- {
- /* Compare two strings, but at most n characters. */
-
- while (n-- != 0) {
- if (*s1 != *s2) return(*s1 - *s2);
- if (*s1 == 0) return(0);
- s1++;
- s2++;
- }
- return 0;
- }
-
-
- /* These functions based on the similar operations in date.c. No support
- * for timezones or daylight saving - localtime() currently just calls
- * gmtime() and returns the results unmodified.
- */
-
- #include <time.h>
-
- static int _days_per_month[] =
- { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
-
- static char *_months[] =
- { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-
- static char *_days[] =
- { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-
- static long _s_p_min = 60L;
- static long _s_p_hour = 60L * 60L;
- static long _s_p_day = 60L * 60L * 24L;
- static long _s_p_year = 60L * 60L * 24L * 365L;
-
-
- char *asctime(tmp)
- struct tm *tmp;
- {
- static char _asctbuf[30];
-
- sprintf(_asctbuf,"%s %s %2d %02d:%02d:%02d %d\n",
- _days[tmp->tm_wday], _months[tmp->tm_mon],
- tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec,
- tmp->tm_year + 1900);
-
- return _asctbuf;
- }
-
- struct tm *gmtime(ltp)
- long *ltp;
- {
- static struct tm _tm;
- char *p;
- long t = *ltp;
-
- for (p = (char *)&_tm; p < ((char *)&_tm) + sizeof(_tm); p++)
- *p = 0;
-
- /* get day-of-week. (Add 4 because 1.1.70 was a Thursday) */
- _tm.tm_wday = ((t / _s_p_day)+4L) % 7L;
-
- /* reduce t by years and days, leaving seconds this year. */
- while (t >= _s_p_year) {
- if (((_tm.tm_year + 2) % 4) == 0)
- t -= _s_p_day;
- _tm.tm_year += 1;
- t -= _s_p_year;
- }
- _tm.tm_yday = t / _s_p_day;
-
- /* adjust february for leap year and reduce to seconds this month */
- if (((_tm.tm_year + 2) % 4) == 0)
- _days_per_month[1]++;
- while ( t >= (_days_per_month[_tm.tm_mon] * _s_p_day))
- t -= _days_per_month[_tm.tm_mon++] * _s_p_day;
- _days_per_month[1] = 28;
- _tm.tm_year += 70;
-
- /* find day of month */
- _tm.tm_mday = 1;
- while (t >= _s_p_day) {
- t -= _s_p_day;
- _tm.tm_mday++;
- }
-
- /* and time today */
- while (t >= _s_p_hour) {
- t -= _s_p_hour;
- _tm.tm_hour++;
- }
- while (t >= _s_p_min) {
- t -= _s_p_min;
- _tm.tm_min++;
- }
- _tm.tm_sec = (int) t;
-
- return &_tm;
- }
-
- struct tm *localtime(ltp)
- long *ltp;
- {
- return gmtime(ltp);
- }
-
- The following fixes may be regarded as dubious, since they actually make
- the operation of minix less like standard unix. However, the performance
- of unbuffered tty output (as used in kermit help messages and command
- completion) is so appalling I felt something was needed.
-
- If a call is made to any of the string output routines (printf, puts ..),
- when the channel is unbuffered, then a temporary buffer is attached to
- the channel for the duration of the call and flushed at the end. Since
- setting stdout unbuffered freed it's buffer allocation, the buffer used
- for this is probably already available on the heap.
-
- The speed-up effect of this on unbuffered i/o (as used for all stderr
- in the system utilities) is startling, and I can't think why these changes
- should cause any incompatibilities.
-
-
- #include <stdio.h>
-
- /* tmpbuf() creates a temporary buffer for the file, if possible.
- * Only call this if the file is unbuffered.
- */
-
- static char *_locbuf = NULL;
- static int _locflags;
-
- static void tmpbuf(file)
- register FILE *file;
- {
- if (_locbuf == NULL) /* no buffer - get one */
- _locbuf = malloc(BUFSIZ);
-
- if (_locbuf == NULL) /* no space - cannot buffer */
- return;
-
- _locflags = file->_flags;
- file->_flags &= ~UNBUFF;
- file->_buf = file->_ptr = _locbuf;
- file->_count = 0;
- }
-
- /* endbuf() restores the FILE flags and flushes the buffer if required. */
-
- static endbuf(file, fflag)
- register FILE *file;
- int fflag;
- {
- if (fflag || file->_buf == _locbuf)
- fflush(file);
-
- if (file->_buf == _locbuf && _locbuf != NULL) {
-
- /* temporary buffer used - restore old settings */
- file->_buf = file->_ptr = NULL;
- file->_count = 0;
- file->_flags = _locflags;
- }
- }
-
-
-
- /* puts() - replaces macro in stdio.h, since a newline is required
- * after the given string.
- */
-
- puts(s)
- register char *s;
- {
- if ( testflag(stdout, UNBUFF) )
- tmpbuf(stdout);
-
- while ( *s )
- putc(*s++,stdout);
- putc('\n',stdout);
-
- endbuf(stdout, 1); /* always flush */
- }
-
-
- fputs(s,file)
- register char *s;
- FILE *file;
- {
- if ( testflag(file, UNBUFF) )
- tmpbuf(file);
-
- while ( *s )
- putc(*s++,file);
-
- endbuf(file, 0);
- }
-
-
-
- fprintf (file, fmt, args)
- FILE *file;
- char *fmt;
- int args;
- {
- if ( testflag(file, UNBUFF) )
- tmpbuf(file);
-
- _doprintf (file, fmt, &args);
-
- endbuf(file, testflag(file, PERPRINTF));
- }
-
-
- printf (fmt, args)
- char *fmt;
- int args;
- {
- if ( testflag(stdout, UNBUFF) )
- tmpbuf(stdout);
-
- _doprintf (stdout, fmt, &args);
-
- endbuf(stdout, testflag(stdout, PERPRINTF));
- }
-
-
-
- Changes to Minix
- ================
-
- Here's where it gets more experimental: I've done the following fixes
- to MINIX in order to get Kermit working or to solve problems I've found along
- the way. Some may not be essential - I didn't want to rebuild with all my
- older fixes taken out just to determine which are necessary. Since all the
- fixes are for major problems, they're worth doing anyway.
-
-
- Any SIGALRM sent to a process will cancel all pending alarms. This
- has the effect of suspending forever (say) the update process if another
- process executes sleep(). MM/signal.c, check_sig() resets not only the
- alarm bit for process getting the signal, but for all others. Resetting the
- bit needs to be conditional on the state of the send_sig flag. The clock
- task looks in the message field CLOCK_PROC_NR for the process number of
- the task that needs the alarm signal. MM actually puts the number in the
- field PROC_NR, which is not the same. Hence the clock actually sends the
- signal to process 0, which is taken to mean all processes.
-
- It is possible for a message from FS to a task to be overwritten
- before it reaches its destination if the task supports multiple minor
- devices and process suspension, as a TTY driver with more than one line
- would. This will occur if the TTY driver has suspended a task, and is busy
- with an interrupt request (which will revive the task) when FS send-receives
- a message about a different minor device. FS will be forced to wait for the
- task until the REVIVE message appears. FS is then sent the message, since it
- appears to be waiting for a reply. The tty task will then look for more
- messages and will find the FS request, now overwritten with the REVIVE message.
- The TTY will be given its own REVIVE message and reject it as invalid. FS
- will continue to wait for a reply to it's TTY request (now lost) and hang.
-
- I have fixed this by treating REVIVE messages in the same way as
- signals they are rather similar, since they are sent to a caller which is not
- waiting for them. Instead of replying with a REVIVE message, I call
- set_revive(), a function similar to cause_sig() which sets a flag in the
- proc table p_flags and saves the reply status in a new member, p_status.
- Inform() is then called for FS as well as MM, and searches for the revive
- flag. Any pending revives are actioned as a HARDWARE message, just like a
- kernel signal. FS will then no longer get a reply from a previous message
- when send-receiving to a task, since the REVIVE call comes from HARDWARE.
- FS can thus be simplified in areas like rw_dev().
-
- FS may panic with a "couldn't revive anyone" error when it recovers
- a process from waiting on a broken pipe. If the pipe is reading from two file
- descriptors (as in the Kermit procedure to pipe process stdout and stderr to
- a remote machine for the REMOTE HOST command), then FS/misc.c, do_exit() will
- call FS/open.c, do_close() for both descriptors. On each call, FS/pipe.c,
- release() will be called, setting the REVIVING flag for the reading process (e.g. the
- Kermit) twice, and also incrementing 'reviving'. FS/main.c, get_work() revives
- the reading process, then can't find a process to account for the other
- increment of 'reviving'. release() should check for the REVIVING flag before
- calling revive() again.
-
- FS can send a cancel message for an out-of-range tty number when a
- process is killed, due to FS/pipe.c, do_unpause() not setting up the global
- fp before calling get_filp(). The tty driver does not catch out-of-range line
- numbers on either messages or characters from the overrun buffer, and can
- index a tty structure that doesn't exist.
-
- Do_unpause() doesn't check that get_filp() returns a valid result.
- The return can be NULL if the file (that the process is paused on) has been
- closed. This happens if a signal killed the process. The distributed code of
- MM/signal.c, check_sig() differs from the printed listings in Tanenbaum's
- book in the order of execution of unpause() and sig_proc(). This means that
- FS expects do_unpause to have a process to restart, when the filehandles it
- should look at are already closed. It seems OK to return the order of the
- calls to their original state, but I'd like to know why they changed.
-
- CONTROL-S will pause the user process by waiting before replying to
- FS. This causes the entire system to get stuck waiting for FS to return from
- the TTY call. The TTY needs to SUSPEND the process on output, just as it does
- on input. Do_cancel() in the TTY driver also has to change to ensure that FS
- obtains a reply or a REVIVE when output is completed.
-
- The following comments are not bug reports, but code changes I have done
- to improve the system's performance at high interrupt rates. I found
- character processing at 9600 baud on an 8MHz 8086 a problem, and had to
- make several changes :
-
- When interrupts are arriving at high speed (characters at 9600 baud),
- some may be lost due to the time taken to process an interrupt in the kernel.
- This time may be reduced by moving the check for now-ready tasks in kernel/
- proc.c, interrupt() to the end of mini-rec(). This reduces the number of calls
- to mini-send within the interrupt handler to 1. If a clock interrupt was
- pending, the clock processing can double or triple the time needed to process
- the call to interrupt(). If the tty task is often scheduled, the clock task
- is often behind, since tasks do not preempt each other. Thus clock interrupts
- often are pending when interrupt() is called at high repetition rates.
-
- However, placing this check in mini_rec() will mean that interrupts
- will always be serviced when the task becomes ready. This, while reducing the
- interrupt latency of a task, means that fast interrupts can make the task
- permanently computable. No other process can then obtain any time to read
- the incoming characters, and the task's buffer fills. Flowcontrol and possibly
- ignoring unbufferable input makes it possible to empty the buffer again.
- File transfer with Kermit is OK, because the packets are considerably smaller
- than the task's buffer, but long packets, sliding windows and connect sessions
- will have problems if flowcontrol is not possible.
-
- The mini_send() call in interrupt() should also be removed and the
- message copy be performed directly within interrupt(), avoiding the
- substantial checking performed on call parameters, which should be unnecessary
- on messages within the kernel. The message copy might also be performed by
- a faster version of copy_mess(), for use within the kernel, which need not
- span segments. Better still, since interrupts may overwrite each other and
- only affect a task's private data area, they tend to use a single known
- buffer for data transfer to the calling task. (I haven't made these last 2 changes yet).
-
-
-
- The following code fragments indicate most of the above fixes. Since the code
- was originally that distributed by Prentice-Hall, I have included their
- copyright notice.
-
- /* Copyright (C) 1987 by Prentice-Hall, Inc. Permission is hereby granted to
- * private individuals and educational institutions to modify and
- * redistribute the binary and source programs of this system to other
- * private individuals and educational institutions for educational and
- * research purposes. For corporate or commercial use, permission from
- * Prentice-Hall is required. In general, such permission will be granted,
- * subject to a few conditions.
- */
-
- =============
- Kernel/proc.c
- =============
-
- /*===========================================================================*
- * interrupt *
- *===========================================================================*/
- PUBLIC interrupt(task, m_ptr)
- int task; /* number of task to be started */
- message *m_ptr; /* interrupt message to send to the task */
- {
- /* An interrupt has occurred. Schedule the task that handles it. */
-
- int this_bit;
- register struct proc *dest_ptr;
-
- #ifdef ibmpc
- /* Re-enable the 8259A interrupt controller. */
- this_bit = 1 << (-task);
- port_out(INT_CTL, ENABLE); /* this re-enables the 8259A controller chip */
- if (pc_at) port_out(INT2_CTL, ENABLE); /* re-enable second 8259A */
- #endif
-
- /* Try to send the interrupt message to the indicated task. */
- this_bit = 1 << (-task);
- dest_ptr = proc_addr(task);
- if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING)
- && (dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == HARDWARE) ) {
-
- /* Destination is indeed waiting for this message. */
- cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys, m_ptr,
- dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf);
- dest_ptr->p_flags &= ~RECEIVING; /* deblock destination */
- if (dest_ptr->p_flags == 0) ready(dest_ptr);
- busy_map &= ~this_bit; /* turn off the bit in case it was on */
-
- } else {
-
- /* The message could not be sent to the task; it was not waiting. */
- if (task == CLOCK) {
- lost_ticks++;
- } else {
- busy_map |= this_bit; /* mark task as busy */
- task_mess[-task] = m_ptr; /* record message pointer */
- }
- }
-
- /* If a task has just been readied and a user is running, run the task. */
- if (rdy_head[TASK_Q] != NIL_PROC && (cur_proc >= 0 || cur_proc == IDLE))
- pick_proc();
- }
-
- /*===========================================================================*
- * mini_send *
- *===========================================================================*/
- PUBLIC int mini_send(caller, dest, m_ptr)
- int caller; /* who is trying to send a message? */
- int dest; /* to whom is message being sent? */
- message *m_ptr; /* pointer to message buffer */
- {
- /* Send a message from 'caller' to 'dest'. If 'dest' is blocked waiting for
- * this message, copy the message to it and unblock 'dest'. If 'dest' is not
- * waiting at all, or is waiting for another source, queue 'caller'.
- */
-
- register struct proc *caller_ptr, *dest_ptr, *next_ptr;
- vir_bytes vb; /* message buffer pointer as vir_bytes */
- vir_clicks vlo, vhi; /* virtual clicks containing message to send */
- vir_clicks len; /* length of data segment in clicks */
-
- /* User processes are only allowed to send to FS and MM. Check for this. */
- if (caller >= LOW_USER && (dest != FS_PROC_NR && dest != MM_PROC_NR))
- #ifdef DEBUG
- if (dest != SYSTASK) return(E_BAD_DEST);
- #else
- return(E_BAD_DEST);
- #endif
- caller_ptr = proc_addr(caller); /* pointer to source's proc entry */
- dest_ptr = proc_addr(dest); /* pointer to destination's proc entry */
- if (dest_ptr->p_flags & P_SLOT_FREE) return(E_BAD_DEST); /* dead dest */
-
- /* Check for messages wrapping around top of memory or outside data seg. */
- len = caller_ptr->p_map[D].mem_len;
- vb = (vir_bytes) m_ptr;
- vlo = vb >> CLICK_SHIFT; /* vir click for bottom of message */
- vhi = (vb + MESS_SIZE - 1) >> CLICK_SHIFT; /* vir click for top of message */
- if (vhi < vlo || vhi - caller_ptr->p_map[D].mem_vir >= len)return(E_BAD_ADDR);
-
- /* Check to see if 'dest' is blocked waiting for this message. */
-
- if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING) &&
- (dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == caller) ) {
-
- /* Destination is indeed waiting for this message. */
- cp_mess(caller, caller_ptr->p_map[D].mem_phys, m_ptr,
- dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf);
- dest_ptr->p_flags &= ~RECEIVING; /* deblock destination */
- if (dest_ptr->p_flags == 0) ready(dest_ptr);
-
- } else {
- /* Destination is not waiting. Block and queue caller. */
- if (caller == HARDWARE) return(E_OVERRUN);
- caller_ptr->p_messbuf = m_ptr;
- caller_ptr->p_flags |= SENDING;
- unready(caller_ptr);
-
- /* Process is now blocked. Put in on the destination's queue. */
- if ( (next_ptr = dest_ptr->p_callerq) == NIL_PROC) {
- dest_ptr->p_callerq = caller_ptr;
- } else {
- while (next_ptr->p_sendlink != NIL_PROC)
- next_ptr = next_ptr->p_sendlink;
- next_ptr->p_sendlink = caller_ptr;
- }
- caller_ptr->p_sendlink = NIL_PROC;
- }
-
- #ifdef DEBUG
- /* This message has been successfully handled. See if it should be saved
- * in the message log.
- */
- if ((save_flags[caller+NR_TASKS] & CALL_LOG)
- || (save_flags[dest +NR_TASKS] & DEST_LOG))
- save_msg(caller, dest, m_ptr);
- #endif
-
- return(OK);
- }
-
-
- /*===========================================================================*
- * mini_rec *
- *===========================================================================*/
- PRIVATE int mini_rec(caller, src, m_ptr)
- int caller; /* process trying to get message */
- int src; /* which message source is wanted (or ANY) */
- message *m_ptr; /* pointer to message buffer */
- {
- /* A process or task wants to get a message. If one is already queued,
- * acquire it and deblock the sender. If no message from the desired source
- * is available, block the caller. No need to check parameters for validity.
- * Users calls are always sendrec(), and mini_send() has checked already.
- * Calls from the tasks, MM, and FS are trusted.
- */
-
- register struct proc *caller_ptr, *sender_ptr, *prev_ptr;
- int sender, this_bit;
- int locked = FALSE;
-
- caller_ptr = proc_addr(caller); /* pointer to caller's proc structure */
-
- /* if we're ready to receive, find a sender. Else just block. */
- if ((caller_ptr->p_flags & SENDING) == 0) {
-
- /* Check to see if a message from desired source is already available. */
-
- sender_ptr = caller_ptr->p_callerq;
- while (sender_ptr != NIL_PROC) {
- sender = sender_ptr - proc - NR_TASKS;
- if (src == ANY || src == sender) {
- /* An acceptable message has been found. */
- cp_mess(sender, sender_ptr->p_map[D].mem_phys, sender_ptr->p_messbuf,
- caller_ptr->p_map[D].mem_phys, m_ptr);
- sender_ptr->p_flags &= ~SENDING; /* deblock sender */
- if (sender_ptr->p_flags == 0) ready(sender_ptr);
- if (sender_ptr == caller_ptr->p_callerq)
- caller_ptr->p_callerq = sender_ptr->p_sendlink;
- else
- prev_ptr->p_sendlink = sender_ptr->p_sendlink;
- return(OK);
- }
- prev_ptr = sender_ptr;
- sender_ptr = sender_ptr->p_sendlink;
- }
-
- /* If the caller is a task, there may be an interrupt waiting.
- * Check now, rather than before checking callers, so fast interrupts
- * give the process a chance to absorb them. Lock between determining the
- * state of busy_map and setting RECEIVING so an interrupt occurring
- * between cannot be forgotten.
- */
-
- if (caller < HARDWARE && (src == ANY || src == HARDWARE)) {
-
- lock();
- locked = TRUE;
- this_bit = 1 << (-caller);
- if (busy_map & this_bit) {
- cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys,
- task_mess[-caller], caller_ptr->p_map[D].mem_phys,
- m_ptr);
- busy_map &= ~this_bit; /* must be locked here too */
- restore();
- return(OK);
- }
- }
- }
-
- /* No suitable message is available. Block the process trying to receive. */
-
- caller_ptr->p_getfrom = src;
- caller_ptr->p_messbuf = m_ptr;
- caller_ptr->p_flags |= RECEIVING;
- if (locked) restore();
- unready(caller_ptr);
-
- /* If MM has just blocked and there are kernel signals pending, now is the
- * time to tell MM about them, since it will be able to accept the message.
- * Also applies to revive messages for FS. No problems with receive-before-
- * send, since (src == ANY) cannot be true for sendrec messages.
- */
-
- if (rev_procs > 0 && caller == FS_PROC_NR && src == ANY) inform(FS_PROC_NR);
- if (sig_procs > 0 && caller == MM_PROC_NR && src == ANY) inform(MM_PROC_NR);
- return(OK);
- }
-
- ===============
- kernel\system.c
- ===============
-
- /*===========================================================================*
- * set_revive *
- *===========================================================================*/
- PUBLIC set_revive(proc_nr, status)
- int proc_nr;
- int status;
- /* Similar function to cause_sig. Tasks sending a revive message directly
- * to FS may collide with a send in the opposite direction. Therefore, this
- * function saves the event and associated status (error code, bytes read etc)
- * in the target process' proc entry, and the kernel informs FS later with
- * inform(FS_PROC_NR). It would be nice to share the proc table's p_pending
- * member with MM, but the signals recorded in it may be ignored - only MM
- * knows. Note that the device's id is lost, and only FS may receive a message.
- * This is not currently a problem.
- */
- {
- register struct proc *rp;
-
- rp = proc_addr(proc_nr);
- if (rp->p_flags & NEED_REVIVE)
- panic ("cannot revive twice",proc_nr);
- rp->p_flags |= NEED_REVIVE;
- rp->p_rstatus = status;
- rev_procs++;
-
- inform(FS_PROC_NR); /* perhaps it's safe to do it now ? */
- }
-
- /*===========================================================================*
- * inform *
- *===========================================================================*/
- PUBLIC inform(proc_nr)
- int proc_nr; /* MM_PROC_NR or FS_PROC_NR */
- {
- /* When a signal is detected by the kernel (e.g., DEL), or generated by a task
- * (e.g. clock task for SIGALRM), cause_sig() is called to set a bit in the
- * p_pending field of the process to signal. Then inform() is called to see
- * if MM is idle and can be told about it. Whenever MM blocks, a check is
- * made to see if 'sig_procs' is nonzero; if so, inform() is called.
- * Likewise REVIVE messages for FS. These are sent only when FS is ready to
- * receive. Code here changed to check recipient is not blocked on send as
- * well as receive.
- */
-
- register struct proc *rp, *mmp;
- int r;
-
- /* If MM/FS is not waiting for new input, forget it. */
- mmp = proc_addr(proc_nr);
- if ( ((mmp->p_flags & RECEIVING) == 0) || mmp->p_getfrom != ANY) return;
-
- /* If MM is waiting for new input, find a process with pending signals. */
- if (proc_nr == MM_PROC_NR) {
- for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++)
- if (rp->p_pending != 0) {
- m.m_type = KSIG;
- m.PROC1 = rp - proc - NR_TASKS;
- m.SIG_MAP = rp->p_pending;
- sig_procs--;
- if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK)
- panic("can't inform MM : ", r);
- rp->p_pending = 0; /* the ball is now in MM's court */
- return;
- }
- }
-
- /* If FS is waiting for new input, find a process with pending revive. */
- else if (proc_nr == FS_PROC_NR) {
- for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++)
- if (rp->p_flags & NEED_REVIVE) {
- m.m_type = REVIVE;
- m.REP_PROC_NR = rp - proc - NR_TASKS;
- m.REP_STATUS = rp->p_rstatus;
- rev_procs--;
- rp->p_flags &= ~NEED_REVIVE;
- if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK)
- panic("can't inform FS : ", r);
- return; /* the ball is now in FS's court */
- }
- }
- }
-
- ===========
- mm/signal.c
- ===========
-
- /*===========================================================================*
- * check_sig *
- *===========================================================================*/
- PRIVATE int check_sig(proc_id, sig_nr, send_uid)
- int proc_id; /* pid of process to signal, or 0 or -1 */
- int sig_nr; /* which signal to send (1-16) */
- uid send_uid; /* identity of process sending the signal */
- {
- /* Check to see if it is possible to send a signal. The signal may have to be
- * sent to a group of processes. This routine is invoked by the KILL system
- * call, and also when the kernel catches a DEL or other signal. SIGALRM too.
- */
-
- register struct mproc *rmp;
- int count, send_sig;
- unshort mask;
- extern unshort core_bits;
-
- if (sig_nr < 1 || sig_nr > NR_SIGS) return(EINVAL);
- count = 0; /* count # of signals sent */
- mask = 1 << (sig_nr - 1);
-
- /* Search the proc table for processes to signal. Several tests are made:
- * - if proc's uid != sender's, and sender is not superuser, don't signal
- * - if specific process requested (i.e., 'procpid' > 0, check for match
- * - if a process has already exited, it can't receive signals
- * - if 'proc_id' is 0 signal everyone in same process group except caller
- */
- for (rmp = &mproc[INIT_PROC_NR + 1]; rmp < &mproc[NR_PROCS]; rmp++ ) {
- if ( (rmp->mp_flags & IN_USE) == 0) continue;
- send_sig = TRUE; /* if it's FALSE at end of loop, don't signal */
- if (send_uid != rmp->mp_effuid && send_uid != SUPER_USER)send_sig=FALSE;
- if (proc_id > 0 && proc_id != rmp->mp_pid) send_sig = FALSE;
- if (rmp->mp_flags & HANGING) send_sig = FALSE; /*don't wake the dead*/
- if (proc_id == 0 && mp->mp_procgrp != rmp->mp_procgrp) send_sig = FALSE;
- if (send_uid == SUPER_USER && proc_id == -1) send_sig = TRUE;
-
- /* SIGALARM is a little special. When a process exits, a clock signal
- * can arrive just as the timer is being turned off. Also, turn off
- * ALARM_ON bit when timer goes off to keep it accurate.
- */
- /* Change : only reset the ALARM_ON bit for processes that are
- * going to get the signal - don't wipe out all the other alarms!
- */
-
- if (send_sig && sig_nr == SIGALRM) {
- if ( (rmp->mp_flags & ALARM_ON) == 0) continue;
- rmp->mp_flags &= ~ALARM_ON;
- }
-
- if (send_sig == FALSE || rmp->mp_ignore & mask) continue;
-
- /* If process is hanging on PAUSE, WAIT, tty, pipe, etc. release it. */
- unpause(rmp - mproc); /* check to see if process is paused */
- count++;
-
- /* Send the signal or kill the process, possibly with core dump. */
- sig_proc(rmp, sig_nr);
-
- if (proc_id > 0) break; /* only one process being signalled */
- }
-
- /* If the calling process has killed itself, don't reply. */
- if ((mp->mp_flags & IN_USE) == 0 || (mp->mp_flags & HANGING))dont_reply =TRUE;
- return(count > 0 ? OK : ESRCH);
- }
-
-
- /*===========================================================================*
- * set_alarm *
- *===========================================================================*/
- PUBLIC int set_alarm(proc_nr, sec)
- int proc_nr; /* process that wants the alarm */
- unsigned sec; /* how many seconds delay before the signal */
- {
- /* This routine is used by do_alarm() to set the alarm timer. It is also
- * to turn the timer off when a process exits with the timer still on.
- */
-
- int remaining;
-
- m_sig.m_type = SET_ALARM;
-
- /*--- m_sig.PROC_NR = proc_nr; ---*/
- m_sig.CLOCK_PROC_NR = proc_nr; /* clock uses different member */
-
- m_sig.DELTA_TICKS = HZ * sec;
- if (sec != 0)
- mproc[proc_nr].mp_flags |= ALARM_ON; /* turn ALARM_ON bit on */
- else
- mproc[proc_nr].mp_flags &= ~ALARM_ON; /* turn ALARM_ON bit off */
-
- /* Tell the clock task to provide a signal message when the time comes. */
- if (sendrec(CLOCK, &m_sig) != OK) panic("alarm er", NO_NUM);
- remaining = (int) m_sig.SECONDS_LEFT;
- return(remaining);
- }
-
- =========
- fs/pipe.c
- =========
-
-
- /*===========================================================================*
- * release *
- *===========================================================================*/
- PUBLIC release(ip, call_nr, count)
- register struct inode *ip; /* inode of pipe */
- int call_nr; /* READ or WRITE */
- int count; /* max number of processes to release */
- {
- /* Check to see if any process is hanging on the pipe whose inode is in 'ip'.
- * If one is, and it was trying to perform the call indicated by 'call_nr'
- * (READ or WRITE), release it.
- */
-
- register struct fproc *rp;
-
- /* Search the proc table. */
- for (rp = &fproc[0]; rp < &fproc[NR_PROCS]; rp++) {
- if (rp->fp_suspended == SUSPENDED
- && (rp->fp_fd & BYTE) == call_nr
- && rp->fp_revived != REVIVING
- && rp->fp_filp[rp->fp_fd>>8]->filp_ino == ip) {
- revive(rp - fproc, 0);
- susp_count--; /* keep track of who is suspended */
- if (--count == 0) return;
- }
- }
- }
-
-
- /*===========================================================================*
- * do_unpause *
- *===========================================================================*/
- PUBLIC int do_unpause()
- {
- /* A signal has been sent to a user who is paused on the file system.
- * Abort the system call with the EINTR error message.
- */
-
- register struct fproc *rfp;
- int proc_nr, task, susfd;
- struct filp *f;
- dev_nr dev;
- extern struct filp *get_filp();
-
- if (who > MM_PROC_NR) return(EPERM);
- proc_nr = pro;
- if (proc_nr < 0 || proc_nr >= NR_PROCS) panic("unpause err 1", proc_nr);
- rfp = &fproc[proc_nr];
- if (rfp->fp_suspended == NOT_SUSPENDED) return(OK);
- task = -rfp->fp_task;
-
- if (task != XPIPE) {
- susfd = rfp->fp_fd >> 8;
- f = rfp->fp_filp[susfd]; /* don't use get_filp - fp is wrong */
- if (susfd > NR_FDS || susfd < 0 || f == NIL_FILP) {
- panic("unpause err 4", proc_nr);
- }
- dev = f->filp_ino->i_zone[0]; /* device on which proc is hanging */
- mess.TTY_LINE = (dev >> MINOR) & BYTE;
- mess.PROC_NR = proc_nr;
- mess.m_type = CANCEL;
- if (sendrec(task, &mess) != OK) panic("unpause err 2", NO_NUM);
- while (mess.REP_PROC_NR != proc_nr) {
- revive(mess.REP_PROC_NR, mess.REP_STATUS);
- if (receive(task, &m) != OK) panic("unpause err 3", NO_NUM);
- }
- revive(proc_nr, EINTR); /* signal interrupted call */
- }
-
- return(OK);
- }
-
-
-
- I haven't included my asynchronous tty driver, as I don't use IBM
- hardware and anybody interested in Kermit on Minix probably has their own
- ideas. However, an outline of the driver follows for anybody who might be
- interested (I'm happy to answer any further queries or comments as I
- continue this development).
-
- The tty driver is split into three parts:
-
- tty.c Contains the 'device independent' part of the original driver.
- screen.c Contains hardware-specific code for console screen and
- keyboard driver.
- async.c Contains hardware-specific code for asynchronous serial
- tty driver.
-
-
- The tty_struct structure is extended, and now contains fields for ioctl
- function addresses (so do_ioctl sets characters, modes etc then calls
- the device - dependent code for baud rate, etc) and a single character
- echo function for that port. Tty_ramqueue is a union of the integer queue
- required for screen driving and a char queue for buffering output, ready
- for async interrupts. The flag used for RUNNING / STOPPED indication is
- now a bit mask, another bit being STALLED, to indicate that an XOFF has
- been sent to the device on the other end of the serial line.
-
- TANDEM flowcontrol is implemented in tty.c, flipping the STALLED bit
- and echoing an XOFF or XON when the buffer contents cross high and
- low water marks. Interrupt processing is sufficiently fast that XOFF
- does not need to be sent if the overrun queue is getting full.
-
- Transmitter ready interrupts cause a character to be read from tty_rwords
- and written to the UART. When tty_rwords is empty, a message is sent to
- the tty task, and the queue is refilled. In the meantime, the transmitter
- is disabled to avoid continuous interrupts.
-
- Characters and refill requests are both sent through the same interrupt
- message - use of the suggested TTY_O_DONE message would cause other
- interrupts to be overwritten and lost. The message is now a structure
- consisting of a count, a queue of ints for character and line number,
- and a bit map for refill requests. Thus refill requests are not lost
- if the input queue is full. The kernel interrupt() call is only
- made if the count of queued characters incremented from zero, or a bit
- was set for the first time in the bitmap. This reduces the interrupt
- processing time if an interrupt had already been sent, but had not been
- processed.
-
- A bug exists in the processing of input characters - DEL and QUIT send
- a signal to the proper process only if it is the only login. The calculation
- of the proc to be signalled as 'LOW_USER + line + 1' is wrong, since the
- process slot above the first user is occupied not by the second user's first
- shell but by the update process. For proper processing of tty signals in
- a multiple-user (or multiple screen) system, FS should tell the tty task when
- a tty is controlling a process family.
-
-